上一篇我們拆解了所有教室狀態清單的卡片設計,今天我們要跟隨點擊事件,深入「單一教室完整課表」頁面-AllClassroomsSchedule.kt。
點進這頁時,很多人直覺會想:「既然這是一款找空教室的 App,為什麼這裡列出的反而是『有課時段』?」這背後其實是基於 資訊密度與閱讀體驗(UX) 的考量:

最初規劃這頁面時,我曾設想是否該把每個佔用時段的「課程名稱」(例如:作業系統、計算機概論)一併顯示出來,讓資訊看起來更豐富。
但在與組員深入討論後,我們決定捨棄課程名稱,回歸產品的核心本質:
"X")。若在後台強行塞入課程名稱,不僅增加資料結構與 CSV 解析的複雜度,對只想確認空間空檔的管理者而言更是多餘的資訊噪音。這項團隊討論讓我們確立了「 如無必要,勿增實體 」的原則——在產品設計上,精準滿足核心需求,這遠比堆砌看似華麗卻無關緊要的資訊更為重要。
AllClassroomsSchedule.kt 負責什麼和 Hermes Agent 一起重讀 AllClassroomsSchedule.kt 後,它是管理者從「所有教室狀態總覽」點進某一間教室後,查看該教室完整課表的頁面。
這檔案可以分成以下三大實作重點:
SavedStateHandle + flatMapLatest)// 1. 從 SavedStateHandle 取得傳入的 CLASSROOM_NAME
private val classroomName: LiveData<String?> =
savedStateHandle.getLiveData(ClassroomScheduleDetailActivity.EXTRA_CLASSROOM_NAME)
// 2. 當教室名稱變更時,自動切換查詢對應的課表資料流
val classroomSchedule: LiveData<ClassroomSchedule?> = classroomName.asFlow().flatMapLatest { name ->
if (name != null) repository.getClassroomDetails(name) else flowOf(null)
}.asLiveData()
flatMapLatest 能保證當教室名稱改變時,前一次未完成的資料庫查詢會被自動取消並切換為最新的查詢,最後轉為 LiveData 提供 UI 監聽。// 僅針對值為 "X"(佔用)的時段進行排版輸出
if (getStatusViaReflection(schedule, "$dayKey$p") == "X") {
val timeRange = timeMap[p] ?: ""
val periodName = if (p == "N") "第 N 節" else "第 $p 節"
sb.append("$periodName $timeRange\n")
hasClass = true
}
// 若該天所有節次都不是 "X",則輸出兜底提示
if (!hasClass) {
sb.append("(此日無課)\n")
}
X 代表有課。程式碼逐節掃描,只把被佔用的時段與節次(如 13:40 - 14:30)拼裝出來,遇到整天沒課則補上 (此日無課),確保版面乾淨。private fun getStatusViaReflection(schedule: ClassroomSchedule, fieldName: String): String? {
return try {
val field = schedule::class.java.getDeclaredField(fieldName)
field.isAccessible = true
field.get(schedule) as? String
} catch (e: Exception) {
null
}
}
ClassroomSchedule 擁有超過 45 個時段屬性。若不使用反射,就必須寫出一個包含 45 條分支的巨大 when(fieldName) 判斷式。反射大幅減少了重複的程式碼。AllClassroomsSchedule.kt 承接 AllClassroomsAdapter 中點擊教室卡片後:
AllClassroomsAdapter
「點擊教室卡片」
│
▼
Intent
「取得 CLASSROOM_NAME」
│
▼
SavedStateHandle
「保存並提供教室名稱」
│
▼
ViewModel
「向 Repository 請求課表資料」
│
▼
Repository
「查詢指定教室的資料」
│
▼
ClassroomSchedule
「取得該教室的一週課表」
│
▼
AllClassroomsSchedule.kt
「整理並格式化課表時段」
│
▼
判斷各時段的資料
「X 代表有課」
│
▼
顯示完整課表
「呈現該教室一週的有課時段」
AllClassroomsSchedule.kt 是從所有教室總覽頁點進單一教室後,顯示該教室完整課表的頁面。它的實際 class 名稱是 ClassroomScheduleDetailActivity,layout 是 activity_classroom_schedule_detail.xml。
前一頁 AllClassroomsAdapter 會透過 Intent 傳入 CLASSROOM_NAME,本頁的 ViewModel 透過 SavedStateHandle 取得這個名稱,再用 repository.getClassroomDetails(name) 查詢該教室完整資料。
Activity observe 到 ClassroomSchedule 後,會顯示「教室名稱 課表」,並呼叫 formatScheduleToDisplayString() 把一週課表轉成文字。這個函式會掃描星期一到星期五、每一天第 1、2、3、4、N、5、6、7、8 節,只把欄位值等於 X 的時段列出來,因為 X 代表有課 / 被佔用。
mon1、tue2 這類欄位,但如果名稱打錯,編譯器不會提前抓到、重構欄位名稱時很危險。讀到 AllClassroomsSchedule.kt 時,我看到「所有教室狀態總覽」的最後一段:管理者點進某一間教室後,系統會用 CLASSROOM_NAME 查詢該教室完整課表,並把有課時段整理成文字顯示。
這一頁特別適合再次確認 X 的意義。前面查詢空教室時,我們是用「不是 X」判斷可用;但這一頁標題是「有課時段」,所以它只列出等於 X 的欄位。也就是說,同一份課表資料在不同頁面有不同用途:查詢頁找空閒,詳細頁列佔用。
這個檔案也有幾個值得注意的維護性問題。首先,檔案名稱、class 名稱、layout 名稱不一致,讀專案時需要對照。其次,程式用反射動態取得 mon1、tue2 這類欄位,雖然避免寫很多 when,但欄位名稱寫錯時編譯器不會提前提醒。最後,上課時間、星期與節次都硬編碼在程式裡,如果學校時間表或課程規則改變,就需要修改程式碼。
一句話重點總結:
ClassroomScheduleDetailActivity接收CLASSROOM_NAME,查出該教室完整課表,並把X代表的有課時段格式化顯示。
完成了的管理者功能的最後一部分-「所有教室狀態」後,我們的管理者線也跟著告了一段落。
接下來我們會總結管理者功能,從登入開始,進入後台後分成三條主要功能線:「課表管理」、「教室設定」、「教室狀態」,這些主線是怎麼相輔相成的?下一篇會帶大家看清這三條主線如何各司其職、相互協同,共同撐起整套教室資料維護。